/* ----------------------------------------------------------------------------
* File: skype.cpp
*
* Desc: Initialization functions for Skype plugin
*
* Created: 20/12/2010, by Alexey Korshunov
* ---------------------------------------------------------------------------- */
#include "skype_events.h"
#include "main.h"


using namespace SKYPE4COMLib;
using namespace std;


/************************************************************************/
/* Parsing of the plugin settings from file                             */
/************************************************************************/
static int ParseConfig(PluginSettings *settings)
{
	char buf[512];

	// read config file
	// if it doesn't exist then create
	std::fstream cfgFile;
	cfgFile.open("plugin.cfg", ios::in);
	if (cfgFile.is_open())
	{
		while (!cfgFile.eof())
		{
			cfgFile.get(buf, sizeof(buf));
			cout << buf << endl;
		}

		cfgFile.close();
	}
	else
	{
		// write new config file
		cfgFile.open("plugin.cfg", ios::out);
		if (cfgFile.is_open())
		{
			cfgFile << "chat_topic_control=";
			(settings->bChatTopicControl) ? cfgFile << "true" << endl : (cfgFile << "false" << endl);
			cfgFile << "chat_stat_control=";
			(settings->bChatStatControl) ? cfgFile << "true" << endl : cfgFile << "false" << endl;
			cfgFile << "commands=";
			(settings->bCommands) ? cfgFile << "true" << endl : cfgFile << "false" << endl;
			cfgFile << "discuss=";
			(settings->bDiscuss) ? cfgFile << "true" << endl : cfgFile << "false" << endl;

			cfgFile.close();
		}
	}

	return SKYPE_PLUG_OK;
}


/************************************************************************/
/* Detect our chat                                                      */
/************************************************************************/
static bool IsNecessaryChat(IChatPtr pChat)
{
	bool res = false;
	long Count = 0;

	IUserCollectionPtr pUsersCollection = pChat->GetMembers();
	Count = pUsersCollection->GetCount();

	MyOutputDebugString(TEXT("skype_plugin: Chat name - %s, members - %d, messages - %d"), (LPCTSTR)pChat->GetFriendlyName(), Count, pChat->GetMessages()->GetCount());

	// Condition
	if (Count > 10)
		res = true;

	pUsersCollection = NULL;
	return res;
}


/************************************************************************/
/* Detect necessary chat                                                */
/************************************************************************/
static IChatPtr DetectChat(ISkypePtr pSkype)
{
	IChatPtr pChat = NULL;
	long Count = 0;

	IChatCollectionPtr pChatCollection = pSkype->GetChats();
	Count = pChatCollection->GetCount();
#ifdef _DEBUG
	MyOutputDebugString(TEXT("skype_plugin: Chats count - %d"), Count);
#endif
	for (long i = 1; i <= Count; ++i)
	{
		if (IsNecessaryChat(pChatCollection->GetItem(i)))
		{
			pChat = pChatCollection->GetItem(i);
			break;
		}
	}

	pChatCollection = NULL;
	return pChat;
}


/************************************************************************/
/* Read messages for current chat                                       */
/************************************************************************/
static int ReadMessages(ChatInfo *pChatInfo)
{
	long count = 0, i = 0;
	MessageInfo msgInfo;
	_bstr_t handle;
	IChatMessageCollectionPtr pMsgCollection = NULL;
	IChatMessagePtr pMsg = NULL; 

	if (pChatInfo->pChat == NULL)
		return SKYPE_PLUG_INVALID_ARG;

#ifdef _DEBUG
	MyOutputDebugString(TEXT("skype_plugin: Reading of messages..."));
#endif

	// Parse messages
	pMsgCollection = pChatInfo->pChat->GetMessages();
	count = pMsgCollection->GetCount();
#ifdef _DEBUG
	MyOutputDebugString(TEXT("skype_plugin: Count of messages - %d"), count);
#endif
	for (i = 1; i <= count; ++i)
	{
		pMsg = pMsgCollection->GetItem(i);
		handle = pMsg->GetFromHandle();

		// get message info
		msgInfo.Id = pMsg->GetId();
		msgInfo.body = pMsg->GetBody();
		msgInfo.msgStatus = pMsg->GetStatus();
		msgInfo.msgType = pMsg->GetType();

		EnterCriticalSection(&pChatInfo->csMsg);
		pChatInfo->msgMap[handle].push_back(msgInfo);
		pChatInfo->countMessages++;
#ifdef _DEBUG
		if ((pChatInfo->countMessages % 500) == 0)
			MyOutputDebugString(TEXT("skype_plugin: Was parsed %d messages"), i);
#endif
		LeaveCriticalSection(&pChatInfo->csMsg);
	}

#ifdef _DEBUG
	MyOutputDebugString(TEXT("skype_plugin: OK"));
#endif

	return SKYPE_PLUG_OK;
}


/************************************************************************/
/* Thread function for initialization of chat statistics                */
/************************************************************************/
DWORD WINAPI ChatInitStat(LPVOID lpParam)
{
	int ret = SKYPE_PLUG_OK;
	IChatPtr pChat = NULL;
	ISkypePtr pSkype = NULL;

	SkypeEnv *pSkypeEnv = reinterpret_cast<SkypeEnv*>(lpParam);
	if (pSkypeEnv == NULL)
		ExitThread(0);

	MyOutputDebugString(TEXT("skype_plugin: ChatInitStat() task - start"));

	//CoInitializeEx(NULL, COINIT_MULTITHREADED);
	CoInitialize(NULL);
	//HRESULT hr = CoGetInterfaceAndReleaseStream(pSkypeEnv->pMarshalStreamInit, __uuidof(SKYPE4COMLib::ISkype), (LPVOID *)&pSkype);
	//if (hr != S_OK)
	//	MyOutputDebugString(TEXT("skype_plugin: ChatInitStat() - CoGetInterfaceAndReleaseStream returned error"));

	MyOutputDebugString(TEXT("skype_plugin: ChatInitStat() after CoGetInterfaceAndReleaseStream"));

	//pSkype = pSkypeEnv->skypeInfo.pSkype;

	// Detect necessary chat
	pChat = DetectChat(pSkype);
	if (pChat == NULL)
	{
		MyOutputDebugString(TEXT("skype_plugin: ERROR - Chat not found"));
		pSkypeEnv->bError = true;
		CoUninitialize();
		ExitThread(0);
	}
	MyOutputDebugString(TEXT("skype_plugin: Chat found"));
	pSkypeEnv->chatInfo.pChat = pChat;


	// Read all messages from chat
	if (ReadMessages(&pSkypeEnv->chatInfo) != SKYPE_PLUG_OK)
	{
		pSkypeEnv->bError = true;
		CoUninitialize();
		ExitThread(0);
	}

	CoUninitialize();
	ExitThread(0);
}

/************************************************************************/
/* Function gets chat info                                              */
/************************************************************************/
static int GetChatInfo(SkypeEnv *pSkypeEnv)
{
	DWORD dwThreadId;
	HANDLE hThread;

#ifdef _DEBUG
	MyOutputDebugString(TEXT("################################################################"));
	MyOutputDebugString(TEXT("skype_plugin: Getting of Chat info"));
	MyOutputDebugString(TEXT("skype_plugin: GetChatInfo() task - start"));
#endif

	// ChatInitStat() thread
	//HRESULT hr = CoMarshalInterThreadInterfaceInStream(__uuidof(SKYPE4COMLib::ISkype), pSkypeEnv->skypeInfo.pSkype, &pSkypeEnv->pMarshalStreamInit);
	//if (hr != S_OK)
	//	MyOutputDebugString(TEXT("skype_plugin: CoMarshalInterThreadInterfaceInStream returned 0x%08x"), hr);

	hThread = CreateThread(NULL, 0, ChatInitStat, (LPVOID)pSkypeEnv, 0, &dwThreadId);
	if (hThread == NULL)
	{
		MyOutputDebugString(TEXT("skype_plugin: ERROR - Unable to create ChatInitStat thread"));
		return SKYPE_PLUG_ERROR;
	}

	// Wait thread
	while(1)
	{
		if (WaitForSingleObject(hThread, THREAD_CHAT_INIT_WAIT) != WAIT_TIMEOUT)
			break;

		pSkypeEnv->serviceStatus.dwCurrentState = SERVICE_RUNNING;
		pSkypeEnv->serviceStatus.dwCheckPoint++;
		SetServiceStatus(pSkypeEnv->hStatus, &pSkypeEnv->serviceStatus);
	}

#ifdef _DEBUG
	OutputDebugString(TEXT("skype_plugin: GetChatInfo() task - finish"));
#endif

	CloseHandle(hThread);
	return SKYPE_PLUG_OK;
}


/************************************************************************/
/* Function gets Skype info                                            */
/************************************************************************/
static int GetSkypeInfo(SkypeEnv *pSkypeEnv)
{
	SkypeInfo *pSkypeInfo = &pSkypeEnv->skypeInfo;

#ifdef _DEBUG
	MyOutputDebugString(TEXT("################################################################"));
	MyOutputDebugString(TEXT("skype_plugin: Getting of Skype info"));
#endif

	// Get client version and print it
	pSkypeInfo->bstrSkypeVersion = pSkypeInfo->pSkype->GetVersion();
#ifdef _DEBUG
	MyOutputDebugString(TEXT("skype_plugin: Skype client version is %s"), (LPCTSTR)pSkypeInfo->bstrSkypeVersion);
#endif

	// Get version of COM wrapper
	pSkypeInfo->bstrWrapperVersion = pSkypeInfo->pSkype->GetApiWrapperVersion();
#ifdef _DEBUG
	MyOutputDebugString(TEXT("skype_plugin: Skype COM wrapper version is %s"), (LPCTSTR)pSkypeInfo->bstrWrapperVersion);
#endif

	return SKYPE_PLUG_OK;
}


/************************************************************************/
/* Create worker threads                                                */
/************************************************************************/
static int CreateWorkerThreads(SkypeEnv *pSkypeEnv, HANDLE *hThreads)
{
	DWORD dwThreadId[COUNT_THREADS];

#ifdef _DEBUG
	MyOutputDebugString(TEXT("################################################################"));
	MyOutputDebugString(TEXT("skype_plugin: Creating of worker threads"));
#endif

	// ChatTopic() thread
	if (pSkypeEnv->settings.bChatTopicControl)
	{
		// create start event
		pSkypeEnv->hStartThreadEvents[CHAT_TOPIC_THREAD_NUM] = CreateEvent(NULL, TRUE, FALSE, NULL);
		if (pSkypeEnv->hStartThreadEvents[CHAT_TOPIC_THREAD_NUM] == NULL)
		{
			MyOutputDebugString(TEXT("skype_plugin: ERROR - Unable to create start event for ChatTopic() thread"));
			return SKYPE_PLUG_ERROR;
		}

		// create thread
		hThreads[CHAT_TOPIC_THREAD_NUM] = CreateThread(NULL, 0, ChatTopic, (LPVOID)pSkypeEnv, 0, &dwThreadId[CHAT_TOPIC_THREAD_NUM]);
		if (hThreads[CHAT_TOPIC_THREAD_NUM] == NULL)
		{
			MyOutputDebugString(TEXT("skype_plugin: ERROR - Unable to create ChatTopic() thread"));
			return SKYPE_PLUG_ERROR;
		}
		MyOutputDebugString(TEXT("skype_plugin: Thread ChatTopic() is created"));
	}


	// ChatStat() thread
	if (pSkypeEnv->settings.bChatStatControl)
	{
		// create start event
		pSkypeEnv->hStartThreadEvents[CHAT_STAT_THREAD_NUM] = CreateEvent(NULL, TRUE, FALSE, NULL);
		if (pSkypeEnv->hStartThreadEvents[CHAT_STAT_THREAD_NUM] == NULL)
		{
			MyOutputDebugString(TEXT("skype_plugin: ERROR - Unable to create start event for ChatStat() thread"));
			return SKYPE_PLUG_ERROR;
		}

		// create thread
		hThreads[CHAT_STAT_THREAD_NUM] = CreateThread(NULL, 0, ChatStat, (LPVOID)pSkypeEnv, 0, &dwThreadId[CHAT_STAT_THREAD_NUM]);
		if (hThreads[CHAT_STAT_THREAD_NUM] == NULL)
		{
			MyOutputDebugString(TEXT("skype_plugin: ERROR - Unable to create ChatStat() worker thread"));
			return SKYPE_PLUG_ERROR;
		}
		MyOutputDebugString(TEXT("skype_plugin: Thread ChatStat() is created"));
	}


	// Commands() thread
	if (pSkypeEnv->settings.bCommands)
	{
		// create start event
		pSkypeEnv->hStartThreadEvents[COMMANDS_THREAD_NUM] = CreateEvent(NULL, TRUE, FALSE, NULL);
		if (pSkypeEnv->hStartThreadEvents[COMMANDS_THREAD_NUM] == NULL)
		{
			MyOutputDebugString(TEXT("skype_plugin: ERROR - Unable to create start event for Commands() thread"));
			return SKYPE_PLUG_ERROR;
		}

		// create thread
		hThreads[COMMANDS_THREAD_NUM] = CreateThread(NULL, 0, Commands, (LPVOID)pSkypeEnv, 0, &dwThreadId[COMMANDS_THREAD_NUM]);
		if (hThreads[COMMANDS_THREAD_NUM] == NULL)
		{
			MyOutputDebugString(TEXT("skype_plugin: ERROR - Unable to create Commands() worker thread"));
			return SKYPE_PLUG_ERROR;
		}
		MyOutputDebugString(TEXT("skype_plugin: Thread Commands() is created"));
	}

	// Quiz
	if (pSkypeEnv->settings.bQuiz)
	{
		// create start event
		pSkypeEnv->hStartThreadEvents[QUIZ_THREAD_NUM] = CreateEvent(NULL, TRUE, FALSE, NULL);
		if (pSkypeEnv->hStartThreadEvents[QUIZ_THREAD_NUM] == NULL)
		{
			MyOutputDebugString(TEXT("skype_plugin: ERROR - Unable to create start event for Quiz thread"));
			return SKYPE_PLUG_ERROR;
		}

		// create thread
		hThreads[QUIZ_THREAD_NUM] = CreateThread(NULL, 0, QuizThread, (LPVOID)pSkypeEnv, 0, &dwThreadId[QUIZ_THREAD_NUM]);
		if (hThreads[QUIZ_THREAD_NUM] == NULL)
		{
			MyOutputDebugString(TEXT("skype_plugin: ERROR - Unable to create QuizThread() thread"));
			return SKYPE_PLUG_ERROR;
		}
		MyOutputDebugString(TEXT("skype_plugin: Thread QuizThread() is created"));
	}
	

	// Wait events about starts of all threads
	for (int i = 0; i < COUNT_THREADS; ++i)
	{
		if (pSkypeEnv->hStartThreadEvents[i] != NULL)
			WaitForSingleObject(pSkypeEnv->hStartThreadEvents[i], INFINITE);
	}
	//WaitForMultipleObjects(COUNT_THREADS, pSkypeEnv->hStartThreadEvents, TRUE, INFINITE);

	// Close start events
	for (int i = 0; i < COUNT_THREADS; ++i)
	{
		if (pSkypeEnv->hStartThreadEvents[i] != NULL)
			CloseHandle(pSkypeEnv->hStartThreadEvents[i]);
	}

#ifdef _DEBUG
	MyOutputDebugString(TEXT("skype_plugin: FINISHED"));
#endif

	return SKYPE_PLUG_OK;
}


/************************************************************************/
/* Initialize Skype plugin                                              */
/************************************************************************/
int InitSkypePlugin(SkypeEnv *pSkypeEnv, HANDLE *hThreads)
{
	int ret = SKYPE_PLUG_OK;
	HRESULT hr = S_OK;
	ISkypePtr pSkype = NULL;

	pSkypeEnv->hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (pSkypeEnv->hStopEvent == NULL)
	{
		MyOutputDebugString(TEXT("skype_plugin: ERROR - Unable to create stop event"));
		return SKYPE_PLUG_ERROR;
	}

	// Parse config file
	if ((ret = ParseConfig(&pSkypeEnv->settings)) != SKYPE_PLUG_OK)
	{
		MyOutputDebugString(TEXT("skype_plugin: ERROR - Unable to read config file"));
		return ret;
	}
	
	// Create Skype object
	//CoInitializeEx(NULL, COINIT_MULTITHREADED);
	CoInitialize(NULL);
	hr = pSkypeEnv->skypeInfo.pSkype.CreateInstance(__uuidof(SKYPE4COMLib::Skype));
	if (hr != S_OK)
	{
		MyOutputDebugString(TEXT("skype_plugin: ERROR - Unable to create Skype instance"));
		return SKYPE_PLUG_ERROR;
	}
	pSkype = pSkypeEnv->skypeInfo.pSkype;

	// Create skype listener and connect events
	pSkypeEnv->skypeInfo.pSinkSkype = new CSkypeEventListener(pSkypeEnv);
	hr = pSkypeEnv->skypeInfo.pSinkSkype->DispEventAdvise(pSkype);
	if (hr != S_OK)
	{
		MyOutputDebugString(TEXT("skype_plugin: ERROR - Unable to connect with Skype event source"));
		return SKYPE_PLUG_ERROR;
	}

	// Start client
	if (pSkype->GetClient()->IsRunning == VARIANT_FALSE)
	{
		pSkype->GetClient()->Start(VARIANT_TRUE, VARIANT_FALSE);
		MyOutputDebugString(TEXT("skype_plugin: Skype client is started"));
	}

	// Connect to Skype API without waiting
	pSkype->Attach(6, VARIANT_FALSE);

#ifdef _DEBUG
	MyOutputDebugString(TEXT("skype_plugin: Attaching process..."));
#endif

	// Wait API permission
	SKYPE4COMLib::TAttachmentStatus attachStatus = pSkype->GetAttachmentStatus();
	while (attachStatus != SKYPE4COMLib::TAttachmentStatus::apiAttachSuccess)
	{
		if (attachStatus == SKYPE4COMLib::TAttachmentStatus::apiAttachRefused)
		{
#ifdef _DEBUG
			MyOutputDebugString(TEXT("skype_plugin: REFUSED"));
#endif
			return SKYPE_PLUG_ERROR;
		}
		else
		{
			// attaching in process, so service is running
			pSkypeEnv->serviceStatus.dwCurrentState = SERVICE_RUNNING;
			pSkypeEnv->serviceStatus.dwCheckPoint++;
			SetServiceStatus(pSkypeEnv->hStatus, &pSkypeEnv->serviceStatus);
		}

		Sleep(SERVICE_SKYPE_ATTACH_SLEEP);
		attachStatus = pSkype->GetAttachmentStatus();
	}
	MyOutputDebugString(TEXT("skype_plugin: ATTACHED"));


	// Get Skype info
	if ((ret = GetSkypeInfo(pSkypeEnv)) != SKYPE_PLUG_OK)
		return ret;

	// Get chat info
	if ((ret = GetChatInfo(pSkypeEnv)) != SKYPE_PLUG_OK)
		return ret;

	// Create worker threads
	if ((ret = CreateWorkerThreads(pSkypeEnv, hThreads)) != SKYPE_PLUG_OK)
		return ret;

	return SKYPE_PLUG_OK;
}


/************************************************************************/
/* Function destroys Skype plugin resources                             */
/************************************************************************/
int DestroySkypePlugin(SkypeEnv *pSkypeEnv, HANDLE *hThreads)
{
	HRESULT hr = S_OK;
	ISkypePtr pSkype = NULL;

	pSkype = pSkypeEnv->skypeInfo.pSkype;

	// Close handles
	for (int i = 0; i < COUNT_THREADS; ++i)
	{
		if (hThreads[i] != NULL)
			CloseHandle(hThreads[i]);
	}
	CloseHandle(pSkypeEnv->hStopEvent);

	// Disconnect events
	if (pSkypeEnv->skypeInfo.pSinkSkype->DispEventUnadvise(pSkype) != S_OK)
	{
		MyOutputDebugString(TEXT("skype_plugin: ERROR - Unable to disconnect with Skype event source"));
		return SKYPE_PLUG_ERROR;
	}
	
	// Destroy Skype object
	pSkypeEnv->skypeInfo.pSkype = NULL;
	CoUninitialize();

	return SKYPE_PLUG_OK;
}